Micron Document
NexusPi Git Node

Node / mirrors / MeshChatX / files / meshchatx / src / backend / websocket_config_guard.py

Displaying Raw • Download

meshchatx/src/backend/websocket_config_guard.py 786bddfe0aebae27a59e4e8aa7a87ae9a1163c12 (786bddfe) Text, 4.92 KB

T8b949e# SPDX-License-Identifier: 0BSD

T8b949e"""WebSocket guards for config updates and authenticated mutators.

Settings that change the HTTP security boundary must go through CSRF-protected
HTTP endpoints, not the unauthenticated config.set WebSocket message.

WebSocket upgrades also enforce a same-authority Origin check. Browsers always
send an Origin header on cross-site WebSocket handshakes, so requiring it to
match the request authority blocks cross-site hijacking of the local daemon
while non-browser clients without an Origin keep working.
"""

Tff7b72from T7ee787__future__ Tff7b72import Te6edf3annotations

Tff7b72import T7ee787logging
Tff7b72from T7ee787urllibT7ee787.T7ee787parse Tff7b72import Te6edf3urlparse

Te6edf3logger Tff7b72= Te6edf3loggingTff7b72.Td2a8ffgetLoggerTb4b4b4(Tff7b72__name__Tb4b4b4)

Te6edf3WEBSOCKET_CONFIG_DENYLIST Tff7b72= Tffa657frozensetTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffauth_enabledTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffauth_password_hashTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)

Te6edf3WEBSOCKET_PUBLIC_TYPES Tff7b72= Tffa657frozensetTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffpingTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)

Te6edf3WEBSOCKET_READ_TYPES Tff7b72= Tffa657frozensetTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffnomadnet.page.archives.getTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffnomadnet.page.archive.loadTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf.forwarding.rules.getTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffkeyboard_shortcuts.getTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)

Te6edf3WEBSOCKET_MUTATOR_TYPES Tff7b72= Tffa657frozensetTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffconfig.setTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffkeyboard_shortcuts.deleteTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffkeyboard_shortcuts.setTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fflxm.generate_paper_uriTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fflxm.ingest_uriTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf.forwarding.rule.addTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf.forwarding.rule.deleteTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf.forwarding.rule.toggleTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffnomadnet.download.cancelTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffnomadnet.file.downloadTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffnomadnet.page.archive.addTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffnomadnet.page.archive.flushTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffnomadnet.page.downloadTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffrns.link.closeTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffrns.link.identifyTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffrns.link.openTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffrns.link.requestTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffrns.link.sendTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)


Tff7b72def Td2a8ffwebsocket_type_requires_authTb4b4b4(Te6edf3msg_typeTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
T8b949e# Fail closed: only explicitly public types skip the session check.
T8b949e# New handlers land in the registry without touching this file, so an
T8b949e# unknown type must not silently run unauthenticated.
Tff7b72if Te6edf3msg_type Tff7b72in Te6edf3WEBSOCKET_PUBLIC_TYPESTb4b4b4:
Tff7b72return Tff7b72False
Tff7b72return Tff7b72True


Tff7b72def Td2a8ff_authority_host_portTb4b4b4(Te6edf3authorityTb4b4b4: Tffa657strTb4b4b4, Te6edf3default_portTb4b4b4: Tffa657intTb4b4b4) Tff7b72-Tff7b72> Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657intTb4b4b4] Tff7b72| Tff7b72NoneTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3parsed Tff7b72= Te6edf3urlparseTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff//Tffd700{Te6edf3authorityTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Te6edf3host Tff7b72= Tb4b4b4(Te6edf3parsedTff7b72.Td2a8ffhostname Tff7b72or Ta5d6ff"Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Te6edf3port Tff7b72= Te6edf3parsedTff7b72.Td2a8ffport
Tff7b72except Tf85149ValueErrorTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72if Tff7b72not Te6edf3hostTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72return Te6edf3hostTb4b4b4, Te6edf3port Tff7b72if Te6edf3port Tff7b72is Tff7b72not Tff7b72None Tff7b72else Te6edf3default_port


Tff7b72def Td2a8ffwebsocket_origin_allowedTb4b4b4(Te6edf3requestTb4b4b4, Te6edf3trusted_proxy_cidrsTb4b4b4: Tffa657str Tff7b72| Tff7b72None Tff7b72= Tff7b72NoneTb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
T8b949e"""Return True when the WebSocket upgrade Origin matches the request authority.

Requests without an Origin header are allowed so local non-browser tooling
keeps working. Browsers always send Origin on cross-site handshakes, which
is what makes this an effective cross-site hijacking defense. When the
direct peer is a trusted reverse proxy, X-Forwarded-Host is accepted as
the public authority.
"""
Te6edf3origin Tff7b72= Te6edf3requestTff7b72.Td2a8ffheadersTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffOriginTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3origin Tff7b72is Tff7b72None Tff7b72or Tff7b72not Te6edf3originTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72True
Tff7b72tryTb4b4b4:
Te6edf3parsed Tff7b72= Te6edf3urlparseTb4b4b4(Te6edf3originTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3parsedTff7b72.Td2a8ffscheme Tff7b72not Tff7b72in Tb4b4b4(Ta5d6ff"Ta5d6ffhttpTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffhttpsTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72False
Tff7b72if Te6edf3parsedTff7b72.Td2a8ffusername Tff7b72or Te6edf3parsedTff7b72.Td2a8ffpasswordTb4b4b4:
Tff7b72return Tff7b72False
Te6edf3origin_port Tff7b72= Te6edf3parsedTff7b72.Td2a8ffport
Tff7b72except Tf85149ValueErrorTb4b4b4:
Tff7b72return Tff7b72False
Te6edf3origin_host Tff7b72= Tb4b4b4(Te6edf3parsedTff7b72.Td2a8ffhostname Tff7b72or Ta5d6ff"Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3origin_hostTb4b4b4:
Tff7b72return Tff7b72False
Tff7b72if Te6edf3origin_port Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3origin_port Tff7b72= T79c0ff443 Tff7b72if Te6edf3parsedTff7b72.Td2a8ffscheme Tff7b72== Ta5d6ff"Ta5d6ffhttpsTa5d6ff" Tff7b72else T79c0ff80

Te6edf3default_port Tff7b72= T79c0ff443 Tff7b72if Te6edf3requestTff7b72.Td2a8ffscheme Tff7b72== Ta5d6ff"Ta5d6ffhttpsTa5d6ff" Tff7b72else T79c0ff80
Te6edf3candidates Tff7b72= Tb4b4b4[Te6edf3requestTff7b72.Td2a8ffhostTb4b4b4]
Tff7b72if Te6edf3trusted_proxy_cidrsTb4b4b4:
Te6edf3remote Tff7b72= Tb4b4b4(Te6edf3requestTff7b72.Td2a8ffremote Tff7b72or Ta5d6ff"Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3remoteTb4b4b4:
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787ip_allowlist Tff7b72import Te6edf3client_ip_allowed

Tff7b72if Te6edf3client_ip_allowedTb4b4b4(Te6edf3remoteTb4b4b4, Te6edf3trusted_proxy_cidrsTb4b4b4)Tb4b4b4:
Te6edf3forwarded_host Tff7b72= Te6edf3requestTff7b72.Td2a8ffheadersTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffX-Forwarded-HostTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3forwarded_hostTb4b4b4:
Te6edf3candidatesTff7b72.Td2a8ffinsertTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3forwarded_hostTff7b72.Td2a8ffsplitTb4b4b4(Ta5d6ff"Ta5d6ff,Ta5d6ff"Tb4b4b4)Tb4b4b4[T79c0ff0Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72for Te6edf3authority Tff7b72in Te6edf3candidatesTb4b4b4:
Tff7b72if Tff7b72not Te6edf3authorityTb4b4b4:
Tff7b72continue
Te6edf3parsed_authority Tff7b72= Te6edf3_authority_host_portTb4b4b4(Te6edf3authorityTb4b4b4, Te6edf3default_portTb4b4b4)
Tff7b72if Te6edf3parsed_authority Tff7b72== Tb4b4b4(Te6edf3origin_hostTb4b4b4, Te6edf3origin_portTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72True
Te6edf3loggerTff7b72.Td2a8ffwarningTb4b4b4(
Ta5d6ff"Ta5d6ffRejected WebSocket upgrade with mismatched Origin Tffd700%rTa5d6ff (Host Tffd700%rTa5d6ff)Ta5d6ff"Tb4b4b4,
Te6edf3originTb4b4b4,
Te6edf3requestTff7b72.Td2a8ffhostTb4b4b4,
Tb4b4b4)
Tff7b72return Tff7b72False


Tff7b72def Td2a8ffsanitize_websocket_config_updateTb4b4b4(Te6edf3configTb4b4b4: Tffa657objectTb4b4b4) Tff7b72-Tff7b72> Tffa657dictTb4b4b4:
T8b949e"""Return a copy of *config* with security-sensitive keys removed."""
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3configTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Tff7b72return Tb4b4b4{Tb4b4b4}

Te6edf3sanitized Tff7b72= Tffa657dictTb4b4b4(Te6edf3configTb4b4b4)
Te6edf3removed Tff7b72= Tb4b4b4[Te6edf3key Tff7b72for Te6edf3key Tff7b72in Te6edf3WEBSOCKET_CONFIG_DENYLIST Tff7b72if Te6edf3key Tff7b72in Te6edf3sanitizedTb4b4b4]
Tff7b72for Te6edf3key Tff7b72in Te6edf3removedTb4b4b4:
Tff7b72del Te6edf3sanitizedTb4b4b4[Te6edf3keyTb4b4b4]

Tff7b72if Te6edf3removedTb4b4b4:
Te6edf3loggerTff7b72.Td2a8ffwarningTb4b4b4(
Ta5d6ff"Ta5d6ffIgnored security-sensitive config keys over WebSocket: Tffd700%sTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Tffa657sortedTb4b4b4(Te6edf3removedTb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72return Te6edf3sanitized

Served by rngit 1.5.2 - Generated in 0.02s